﻿using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Modularity;

namespace Hd.AbpCoding.EntityFrameworkCore
{
    public static class AbpDbContextRegistrationOptionsBuilderExtension
    {
        public static void AddCustomRepositorie<RepositoryInterface, RepositoryImplementation>(this IAbpCommonDbContextRegistrationOptionsBuilder builder)
           where RepositoryInterface : IRepository
           where RepositoryImplementation : class, RepositoryInterface
        {
            
            builder.Services.AddTransient(typeof(RepositoryInterface), typeof(RepositoryImplementation));
        }

        public static void AddCustomRepositories<DbContextInterfaceType, DomainModule, EntityFrameworkCoreModule>(this IAbpCommonDbContextRegistrationOptionsBuilder builder)
           where DbContextInterfaceType : IEfCoreDbContext
           where DomainModule : IAbpModule
           where EntityFrameworkCoreModule : IAbpModule
        {

            var domainTypes = typeof(DomainModule).Assembly.GetTypes();
            var efCoreTypes = typeof(EntityFrameworkCoreModule).Assembly.GetTypes();

            var entities = domainTypes.Where(x => typeof(IEntity).IsAssignableFrom(x) || typeof(IEntity<Guid>).IsAssignableFrom(x));
            foreach (var entityType in entities)
            {
                var primaryKeyType = EntityHelper.FindPrimaryKeyType(entityType);

                var interfaceTypes = domainTypes.Where(x => x.IsInterface);
                // 查找仓储
                var repositories = interfaceTypes
                    .WhereIf(primaryKeyType != null, x => RepositoryTypeCheck(x, entityType, primaryKeyType))
                    .WhereIf(primaryKeyType == null, x => RepositoryTypeCheck(x, entityType))
                    .ToList();
                foreach (var repositoryType in repositories)
                {

                    var repositoryImplementationTypeBase = primaryKeyType == null ?
                        GetRepositoryType(typeof(DbContextInterfaceType), entityType) :
                        GetRepositoryType(typeof(DbContextInterfaceType), entityType, primaryKeyType);

                    var repositoryImplementationType = efCoreTypes.Where(x => repositoryImplementationTypeBase.IsAssignableFrom(x)).FirstOrDefault();
                    if (repositoryImplementationType != null && repositoryImplementationType.IsAssignableTo(repositoryType))
                        builder.Services.AddTransient(repositoryType, repositoryImplementationType);
                }

            }

        }

        private static bool RepositoryTypeCheck(Type target, Type entityType)
        {
            return typeof(IBasicRepository<>).MakeGenericType(entityType).IsAssignableFrom(target) ||
                typeof(IReadOnlyRepository<>).MakeGenericType(entityType).IsAssignableFrom(target) ||
                typeof(IRepository<>).MakeGenericType(entityType).IsAssignableFrom(target);
        }

        private static bool RepositoryTypeCheck(Type target, Type entityType, Type primaryKeyType)
        {
            return typeof(IBasicRepository<,>).MakeGenericType(entityType, primaryKeyType).IsAssignableFrom(target) ||
                typeof(IReadOnlyRepository<,>).MakeGenericType(entityType, primaryKeyType).IsAssignableFrom(target) ||
                typeof(IRepository<,>).MakeGenericType(entityType, primaryKeyType).IsAssignableFrom(target);
        }

        private static Type GetRepositoryType(Type dbContextType, Type entityType)
        {
            return typeof(EfCoreRepository<,>).MakeGenericType(dbContextType, entityType);
        }

        private static Type GetRepositoryType(Type dbContextType, Type entityType, Type primaryKeyType)
        {
            return typeof(EfCoreRepository<,,>).MakeGenericType(dbContextType, entityType, primaryKeyType);
        }
    }
}
